<script setup lang="ts">
import {computed, type CSSProperties, onMounted, onUnmounted, ref} from 'vue'
import type FloatingProp from "@/components/floating/type/FloatingProp.ts";
import type FloatingEmit from "@/components/floating/type/FloatingEmit.ts";

const $emit = defineEmits<FloatingEmit>()

const $prop = withDefaults(defineProps<FloatingProp>(), {
  placement: 'bottom',
  disabled: false,
})

const floatingRef = ref<HTMLElement>()

const computeSize = (size: string | number): string => {
  if (typeof size === 'number') {
    return `${size}px`
  }
  return size
}

/**
 * 浮动框的css样式
 */
let transform = 'translate(0px, 0px)'
const floatingStyle = computed<CSSProperties>(() => {
  const style: CSSProperties = {}
  // 宽度，优先取指定的宽度，如果没有就取被附加元素的宽度
  style.width = $prop.width ? computeSize($prop.width) : floatingRef.value!.offsetWidth + 'px'

  // 最大高度
  if ($prop.maxHeight) {
    style.maxHeight = computeSize($prop.maxHeight)
  }
  // 位移
  if ($prop.offset) {
    let x = 0, y = 0
    if (typeof $prop.offset === 'number') {
      // 单个数字，表示 Y 方向偏移
      y = $prop.offset
    } else if (Array.isArray($prop.offset)) {
      [x, y] = $prop.offset
    }
    // 位移信息需要临时保存起来，避免跟动画中的transform属性冲突
    transform = `translate(${x}px, ${y}px)`
    style.transform = transform
  }

  return style
})

/**
 * 根据触发方式绑定事件，本质是被附加组件的事件会通过冒泡机制被referenceRef监听到
 */
const referenceEvents = computed(() => {
  const events: any = {}

  switch ($prop.trigger) {
    case 'click':
      events.onClick = () => toggle(!$prop.modelValue)
      break

    case 'hover':
      events.onMouseenter = () => toggle(true)
      events.onMouseleave = () => toggle(false)
      break

    case 'focus':
      // 由于focus和blur事件无法冒泡，所以使用onFocusin和onFocusout替代
      events.onFocusin = () => toggle(true)
      events.onFocusout = () => {
        if ($prop.closeOnBlur) {
          toggle(false)
        }
      }
      break

    case 'manual':
      // manual 模式下不绑定任何事件
      break
  }

  return events
})

const toggle = (newValue: boolean) => {
  $emit('update:modelValue', newValue)
}

// 点击外部关闭
const handleClickOutside = (event: MouseEvent) => {
  if (floatingRef.value && !floatingRef.value.contains(event.target as Node)) {
    toggle(false)
  }
}

onMounted(() => {
  document.addEventListener('click', handleClickOutside)
})

onUnmounted(() => {
  document.removeEventListener('click', handleClickOutside)
})
</script>

<template>
  <div class="floating-container" ref="floatingRef">
    <!-- 通过事件冒泡接受被附加组件的事件并处理，然后阻止这些事件进一步冒泡 -->
    <div v-bind="referenceEvents"
         @mouseenter.prevent @mouseleave.prevent @focusin.prevent @focusout.prevent>
      <slot/>
    </div>

    <Transition name="fade">
      <div
          v-if="modelValue"
          class="floating-content"
          :style="floatingStyle"
      >
        <slot name="floating"/>
      </div>
    </Transition>
  </div>
</template>

<style scoped>
.floating-container {
  /* 设置为相对定位，作为绝对定位的参考 */
  position: relative;
  /* 设置为 inline-block 以适应内容宽度 */
  display: inline-block;
}

.floating-content {
  /* 关键：绝对定位不会影响其他元素布局 */
  position: absolute;
  z-index: 9999;

  /* 样式 */
  background: white;
  border-radius: 3px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  overflow: auto;
  transform-origin: left top;
}

/* 添加关键帧动画 */
@keyframes float-in {
  0% {
    opacity: 0;
    transform: v-bind(transform) scale(0);
  }
  100% {
    opacity: 1;
    transform: v-bind(transform) scale(1);
  }
}

@keyframes float-out {
  0% {
    opacity: 1;
    transform: v-bind(transform) scale(1);
  }
  100% {
    opacity: 0;
    transform: v-bind(transform) scale(0);
  }
}

/* Vue transition classes */
.fade-enter-active {
  animation: float-in 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}

.fade-leave-active {
  animation: float-out 0.3s ease forwards;
}

</style>